package edu.spbu.eclipse.xmledit.andrpodk;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.Validator;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
import org.apache.xerces.jaxp.validation.XMLSchemaFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.part.EditorPart;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import com.icl.saxon.Controller;
import com.icl.saxon.ExtendedInputSource;
import com.icl.saxon.ParameterSet;
import com.icl.saxon.TransformerFactoryImpl;
import com.icl.saxon.expr.StringValue;
import com.icl.saxon.om.NamePool;
import com.icl.saxon.style.TerminationException;

import edu.spbu.eclipse.xmledit.andrpodk.editors.XMLEditor;


public class XSLTManager implements IPropertyListener{	
	private static final String docBookXmlSchemaPath="docbook/DocBook V4.5 W3C XML Schema/docbook.xsd";
	private static final String htmlStyleSheetsPath="docbook/docbook-xsl-1.72.0/html/docbook.xsl";
	private static final String pdfStyleSheetsPath="docbook/docbook-xsl-1.72.0/fo/docbook.xsl";
	private XMLEditor xmlEditor;
	private DocumentBuilder builder;
	private FopFactory fopFactory = FopFactory.newInstance();
	private Transformer htmlTransformer=null;
	private Transformer pdfTransformer=null;
	private Validator validator=null;
	private CheckErrorHandler errorHandler=new CheckErrorHandler();
	private TransformErrorHandler errorListener=new TransformErrorHandler();
	private NamePool namePool = NamePool.getDefaultNamePool();
	private Document document;
	private IFile resource;
	private String filePath;	
	private MessageBox okDialog;
	private MessageBox errorDialog;
	private MessageBox overwriteDialog;
	private MessageBox notExist;
	private FileDialog saveDialog;

	public XSLTManager(XMLEditor editor) {		
		xmlEditor=editor;
		Shell shell=xmlEditor.getEditorSite().getShell();
		okDialog=new MessageBox(shell,SWT.OK|SWT.ICON_INFORMATION);
		okDialog.setText("XML Editor");
		errorDialog=new MessageBox(shell,SWT.OK|SWT.ICON_ERROR);
		errorDialog.setText("XML Editor");
		saveDialog=new FileDialog(shell,SWT.SAVE);
		saveDialog.setFilterPath("C:/");
		saveDialog.setText("Specify the output file...");
		overwriteDialog= new MessageBox(shell,SWT.ICON_QUESTION|
				SWT.YES|SWT.NO|SWT.CANCEL);
		overwriteDialog.setText("Specify the output file...");
		overwriteDialog.setMessage("Selected file already exists."+
		"\nDo you want to overwrite it?");
		notExist=new MessageBox(shell,SWT.OK|SWT.ICON_WARNING);
		notExist.setText("Specify the output file...");
		notExist.setMessage("The file path doesn't exist.");
		filePath=((IPathEditorInput)xmlEditor.getEditorInput()).getPath().toString();
		resource=((IFileEditorInput)xmlEditor.getEditorInput()).getFile();
		errorHandler.setResource(resource);
		errorListener.setResource(resource);
		try{
			DocumentBuilderFactoryImpl parserFactory = new DocumentBuilderFactoryImpl();
			parserFactory.setNamespaceAware(true);		    
			builder=parserFactory.newDocumentBuilder();
			builder.setErrorHandler(errorHandler);
		}catch(ParserConfigurationException e){			
		}
	}

	private Document parseXmlFile() {
		if(xmlEditor.isDirty()){			
			xmlEditor.doSave(null);
		}		
		try {
			String systemId = new File(filePath).toURI().toASCIIString();			
			errorHandler.deleteCurrentMarkers();						
			Document doc = builder.parse(new InputSource(systemId));
			if(errorHandler.receivedErrors()){
				throw new SAXException();
			}
			return doc;
		} catch (SAXException e) {			
		} catch (IOException e) {
			IMarker marker;
			try {
				marker = resource.createMarker(IMarker.PROBLEM);				
				marker.setAttribute(IMarker.MESSAGE, e.getMessage());
				marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
				marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
			} catch (CoreException e1) {}			
		}
		return null;
	}

	private boolean isValidated(Document document){
		if(validator==null){
			try{				
				String docbookID=new File(Platform.getInstallLocation().getURL().getFile()
						+"plugins/edu.spbu.eclipse.xmledit.andrpodk_1.0.0/"
						+docBookXmlSchemaPath).toURI().toASCIIString();
				Source[] docBookXmlSchemaSource=new Source[]{new SAXSource(new InputSource(docbookID))}; 
				XMLSchemaFactory schemaFactory=new XMLSchemaFactory();				
				Schema docBookXMLSchema=schemaFactory.newSchema(docBookXmlSchemaSource);
				validator=docBookXMLSchema.newValidator();
				validator.setErrorHandler(errorHandler);
			}catch (SAXException e) {
				errorDialog.setMessage("Internal error:/n"+e.getMessage());
				errorDialog.open();
			}
		}
		try {
			validator.validate(new DOMSource(document));
			if(errorHandler.receivedErrors()){
				throw new SAXException();
			}
			return true;
		} catch (SAXException e) {						
		} catch (IOException e) {
			errorDialog.setMessage("Internal error:/n"+e.getMessage());
			errorDialog.open();
		}
		return false;		
	}

	private boolean checkForSyntaxAndValidate(){
		boolean correct=false;
		document=parseXmlFile();
		if(document!=null){
			if(isValidated(document)){
				correct=true;							
			}else{
				errorDialog.setMessage("The document is invalid.");
				errorDialog.open();
			}
		}else{
			errorDialog.setMessage("There are syntax errors"+ 
			"\n in the document.");
			errorDialog.open();
		}
		return correct;		
	}

	private void transformToHtml(String outputFile, ParameterSet params){
		if(htmlTransformer==null){
			try {
				TransformerFactoryImpl factory = new TransformerFactoryImpl();					
				String sheetID = new File(Platform.getInstallLocation().getURL().getFile()
						+"plugins/edu.spbu.eclipse.xmledit.andrpodk_1.0.0/"
						+htmlStyleSheetsPath).toURI().toASCIIString();
				ExtendedInputSource eis = new ExtendedInputSource(sheetID);
				Source styleSource = new SAXSource(factory.getStyleParser(), eis);
				Templates htmlTemplates=factory.newTemplates(styleSource);					
				htmlTransformer= htmlTemplates.newTransformer();
				htmlTransformer.setErrorListener(errorListener);
			} catch (TransformerConfigurationException e) {										
			}
		}
		((Controller)htmlTransformer).setParams(params);
		try {
			Source sourceInput;								
			sourceInput=new DOMSource(document);                
			htmlTransformer.transform(sourceInput,new StreamResult(new File(outputFile)));
			if(errorListener.receivedErrors()){
				throw new TransformerException("");
			}
			okDialog.setMessage("The transformation has been completed.");
			okDialog.open();
		} catch (TransformerException e) {
			if(e instanceof TerminationException){
				IMarker marker;
				try {
					marker = resource.createMarker(IMarker.PROBLEM);				
					marker.setAttribute(IMarker.MESSAGE, e.getMessage());
					marker.setAttribute(IMarker.LINE_NUMBER,e.getLocator().getLineNumber());
					marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
					marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
				} catch (CoreException e1) {}				
			}
			errorDialog.setMessage("There are some errors"+ 
			"\n during transformation.");
			errorDialog.open();				
		}						
	}

	private void transformToPdf(String selectedFile, ParameterSet params) {
		if(pdfTransformer==null){
			try {
				TransformerFactoryImpl factory = new TransformerFactoryImpl();					
				String sheetID = new File(Platform.getInstallLocation().getURL().getFile()
						+"plugins/edu.spbu.eclipse.xmledit.andrpodk_1.0.0/"
						+pdfStyleSheetsPath).toURI().toASCIIString();
				ExtendedInputSource eis = new ExtendedInputSource(sheetID);
				Source styleSource = new SAXSource(factory.getStyleParser(), eis);								
				pdfTransformer= factory.newTransformer(styleSource);
				pdfTransformer.setErrorListener(errorListener);
			} catch (TransformerConfigurationException e) {										
			}			
		}
		((Controller)pdfTransformer).setParams(params);
		FOUserAgent foUserAgent = fopFactory.newFOUserAgent();		
		OutputStream out=null;
		try {
			out = new java.io.FileOutputStream(selectedFile);
			out = new java.io.BufferedOutputStream(out);
			Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
			pdfTransformer.transform(new DOMSource(document), new SAXResult(fop.getDefaultHandler()));
			out.close();
			if(errorListener.receivedErrors()){
				throw new TransformerException("");
			}
			okDialog.setMessage("The transformation has been completed.");
			okDialog.open();
		} catch (FOPException e) {
			errorDialog.setMessage("Internal error:/n"+e.getMessage());
			errorDialog.open();
		} catch (FileNotFoundException e) {	
			notExist.open();
		}catch (TransformerException e) {			
			if(e instanceof TerminationException){				
				IMarker marker;
				try {
					marker = resource.createMarker(IMarker.PROBLEM);				
					marker.setAttribute(IMarker.MESSAGE, e.getMessage());					
					marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
					marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
				} catch (CoreException e1) {}				
			}			
			errorDialog.setMessage("There are some errors"+ 
			"\n during transformation.");
			errorDialog.open();
		} catch (IOException e) {
			errorDialog.setMessage("Internal error:/n"+e.getMessage());
			errorDialog.open();
		}
	}


	public void performCheckForSyntaxAction() {
		if(parseXmlFile()!=null){
			okDialog.setMessage("The syntax is correct.");
			okDialog.open();
		}else{
			errorDialog.setMessage("There are syntax errors"+ 
			"\n in the document.");
			errorDialog.open();
		}		
	}

	public void performCheckForSyntaxAndValidateAction(){
		if(checkForSyntaxAndValidate()){
			okDialog.setMessage("The document is correct.");
			okDialog.open();			
		}								
	}

	public void performToHtmlAction() {		
		if(checkForSyntaxAndValidate()){
			// TODO paramsDialog
			ParameterSet params = new ParameterSet();
			//  The parameter recommended by Docbook.org doesn't run:
//			int fingerprint = namePool.allocate("", "", "use.extensions");
//			params.put(fingerprint, new StringValue("1"));
			saveDialog.setFileName(null);
			saveDialog.setFilterExtensions(new String[]{"*.html"});
			boolean anotherOpen=true;
			while(anotherOpen){
				String selectedFile=saveDialog.open();
				if(selectedFile!=null){
					if(new File(selectedFile).exists()){						
						switch(overwriteDialog.open()){
						case SWT.YES:							
							transformToHtml(selectedFile, params);					
							anotherOpen=false;
							break;
						case SWT.CANCEL:
							anotherOpen=false;
							break;													
						}
					}else{
						if(new File(selectedFile).getParentFile().exists()){
							transformToHtml(selectedFile, params);
						}else{							 
							notExist.open();							
						}
						anotherOpen=false;
					}
				}else{
					anotherOpen=false;
				}
			}			
		}	
	}

	public void propertyChanged(Object source, int propId) {
		if(propId==EditorPart.PROP_INPUT){
			filePath=((IPathEditorInput)xmlEditor.getEditorInput()).getPath().toString();
			resource=((IFileEditorInput)xmlEditor.getEditorInput()).getFile();
			errorHandler.setResource(resource);
			errorListener.setResource(resource);			
		}
	}

	public void performToPdfAction() {
		if(checkForSyntaxAndValidate()){
			// TODO paramsDialog
			ParameterSet params = new ParameterSet();			
			int fingerprint = namePool.allocate("", "", "use.extensions");
			params.put(fingerprint, new StringValue("1"));
			saveDialog.setFileName(null);
			saveDialog.setFilterExtensions(new String[]{"*.pdf"});
			boolean anotherOpen=true;
			while(anotherOpen){
				String selectedFile=saveDialog.open();
				if(selectedFile!=null){
					if(new File(selectedFile).exists()){						
						switch(overwriteDialog.open()){
						case SWT.YES:							
							transformToPdf(selectedFile, params);					
							anotherOpen=false;
							break;
						case SWT.CANCEL:
							anotherOpen=false;
							break;													
						}
					}else{
						if(new File(selectedFile).getParentFile().exists()){
							transformToPdf(selectedFile, params);
						}else{							 
							notExist.open();							
						}
						anotherOpen=false;
					}
				}else{
					anotherOpen=false;
				}
			}			
		}	
	}	
}